<?php

// +----------------------------------------------------------------------
// | WeChatDeveloper
// +----------------------------------------------------------------------
// | 版权所有 2014~2018 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
// +----------------------------------------------------------------------
// | 官方网站: http://think.ctolog.com
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// +----------------------------------------------------------------------
// | github开源项目：https://github.com/zoujingli/WeChatDeveloper
// +----------------------------------------------------------------------

namespace Itdashu\SwoftThirdPartyService\Service\WeChat\Contracts;

use Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\InvalidArgumentException;
use Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\InvalidResponseException;
use Itdashu\SwoftThirdPartyService\Service\Base\Tools;
use Itdashu\SwoftThirdPartyService\Service\Base\DataArray;

/**
 * Class BasicWeChat
 * @package WeChat\Contracts
 */
class BasicWeChat
{

  /**
   * 当前微信配置
   * @var DataArray
   */
  public $config;

  /**
   * 访问AccessToken
   * @var string
   */
  public $accessToken = '';

  /**
   * 当前请求方法参数
   * @var array
   */
  protected $currentMethod = [];

  /**
   * 当前模式
   * @var bool
   */
  protected $isTry = false;

  /**
   * 注册代替函数
   * @var string
   */
  protected $GetAccessTokenCallback;

  /**
   * BasicWeChat constructor.
   * @param array $options
   */
  public function __construct(array $options)
  {
    if (empty($options['appid'])) {
      throw new InvalidArgumentException('Missing Config -- [appid]');
    }
    if (empty($options['appsecret'])) {
      throw new InvalidArgumentException('Missing Config -- [appsecret]');
    }
    if (isset($options['GetAccessTokenCallback']) && is_callable($options['GetAccessTokenCallback'])) {
      $this->GetAccessTokenCallback = $options['GetAccessTokenCallback'];
    }
    if (!empty($options['cache_path'])) {
      Tools::$cache_path = $options['cache_path'];
    }
    $this->config = new DataArray($options);
  }

  /**
   * 获取访问accessToken
   * @return mixed|string|null
   * @throws InvalidResponseException
   * @throws \Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\LocalCacheException
   */
  public function getAccessToken()
  {
    if (!empty($this->accessToken)) {
      return $this->accessToken;
    }
    $cache = $this->config->get('appid') . '_access_token';
    $this->accessToken = Tools::getCache($cache);
    if (!empty($this->accessToken)) {
      return $this->accessToken;
    }
    // 处理开放平台授权公众号获取AccessToken
    if (!empty($this->GetAccessTokenCallback) && is_callable($this->GetAccessTokenCallback)) {
      $this->accessToken = call_user_func($this->GetAccessTokenCallback, $this->config->get('appid'), $this);
      if (!empty($this->accessToken)) {
        Tools::setCache($cache, $this->accessToken, 7000);
      }
      return $this->accessToken;
    }
    [$appId, $secret] = [$this->config->get('appid'), $this->config->get('appsecret')];
    $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$secret}";
    $result = Tools::jsonDecode(Tools::get($url));
    if (!empty($result['access_token'])) {
      Tools::setCache($cache, $result['access_token'], 7000);
    }
    return $this->accessToken = $result['access_token'];
  }

  /**
   * 设置外部接口 AccessToken
   * 当用户使用自己的缓存驱动时，直接实例化对象后可直接设置 AccessToekn
   * - 多用于分布式项目时保持 AccessToken 统一
   * - 使用此方法后就由用户来保证传入的 AccessToekn 为有效 AccessToekn
   *
   * @param $access_token
   */
  public function setAccessToken($access_token): void
  {
    if (!is_string($access_token)) {
      throw new InvalidArgumentException('Invalid AccessToken type, need string.');
    }
    $cache = $this->config->get('appid') . '_access_token';
    Tools::setCache($cache, $this->accessToken = $access_token);
  }

  /**
   * 清理删除 AccessToken
   * @return bool
   */
  public function delAccessToken(): bool
  {
    $this->accessToken = '';
    return Tools::delCache($this->config->get('appid') . '_access_token');
  }

  /**
   * 以GET获取接口数据并转为数组
   * @param string $url 接口地址
   * @return array|mixed
   * @throws InvalidResponseException
   * @throws \Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\LocalCacheException
   */
  protected function httpGetForJson($url)
  {
    try {
      return Tools::jsonDecode(Tools::get($url));
    } catch (InvalidResponseException $e) {
      if (empty($this->isTry) && isset($this->currentMethod['method']) && in_array((string)$e->getCode(),
          ['40014', '40001', '41001', '42001'], true)) {
        $this->delAccessToken();
        $this->isTry = true;
        return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
      }
      throw new InvalidResponseException($e->getMessage(), $e->getCode());
    }
  }

  /**
   * 以POST获取接口数据并转为数组
   * @param string $url 接口地址
   * @param array $data 请求数据
   * @param bool $buildToJson
   * @return array|mixed
   * @throws InvalidResponseException
   * @throws \Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\LocalCacheException
   */
  protected function httpPostForJson($url, array $data, $buildToJson = true)
  {
    try {
      return Tools::jsonDecode(Tools::post($url, $buildToJson ? Tools::arr2json($data) : $data));
    } catch (InvalidResponseException $e) {
      if (!$this->isTry && in_array((string)$e->getCode(), ['40014', '40001', '41001', '42001'], true)) {
        [$this->delAccessToken(), $this->isTry = true];
        return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
      }
      throw new InvalidResponseException($e->getMessage(), $e->getCode());
    }
  }

  /**
   * 注册当前请求接口
   * @param string $url 接口地址
   * @param string $method 当前接口方法
   * @param array $arguments 请求参数
   * @return mixed
   * @throws InvalidResponseException
   * @throws \Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\LocalCacheException
   */
  protected function registerApi(string &$url, string $method, $arguments = [])
  {
    $this->currentMethod = ['method' => $method, 'arguments' => $arguments];
    if (empty($this->accessToken)) {
      $this->accessToken = $this->getAccessToken();
    }
    return $url = str_replace('ACCESS_TOKEN', $this->accessToken, $url);
  }

  /**
   * 接口通用POST请求方法
   * @param string $url 接口URL
   * @param array $data POST提交接口参数
   * @param bool $isBuildJson
   * @return array|mixed
   * @throws InvalidResponseException
   * @throws \Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\LocalCacheException
   */
  public function callPostApi($url, array $data, $isBuildJson = true)
  {
    $this->registerApi($url, __FUNCTION__, func_get_args());
    return $this->httpPostForJson($url, $data, $isBuildJson);
  }

  /**
   * 接口通用GET请求方法
   * @param string $url 接口URL
   * @return array|mixed
   * @throws InvalidResponseException
   * @throws \Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\LocalCacheException
   */
  public function callGetApi($url)
  {
    $this->registerApi($url, __FUNCTION__, func_get_args());
    return $this->httpGetForJson($url);
  }

}